// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

part of 'declaration_builders.dart';

const Uri? noUri = null;

abstract class ClassMemberAccess {
  /// [Iterator] for all constructors declared in this class or any of its
  /// augmentations.
  ///
  /// Duplicates and augmenting constructor are _not_ included.
  ///
  /// For instance:
  ///
  ///     class Class {
  ///       Class(); // declared, so it is included
  ///       Class.named(); // declared, so it is included
  ///       Class.named(); // duplicate, so it is *not* included
  ///     }
  ///
  ///     augment class Class {
  ///       augment Class(); // augmenting, so it is *not* included
  ///       Class.extra(); // declared, so it is included
  ///     }
  ///
  Iterator<T> fullConstructorIterator<T extends MemberBuilder>();

  /// [NameIterator] for all constructors declared in this class or any of its
  /// augmentations.
  ///
  /// Duplicates and augmenting constructors are _not_ included.
  ///
  /// For instance:
  ///
  ///     class Class {
  ///       Class(); // declared, so it is included
  ///       Class.named(); // declared, so it is included
  ///       Class.named(); // duplicate, so it is *not* included
  ///     }
  ///
  ///     augment class Class {
  ///       augment Class(); // augmenting, so it is *not* included
  ///       Class.extra(); // declared, so it is included
  ///     }
  ///
  NameIterator<T> fullConstructorNameIterator<T extends MemberBuilder>();

  /// [Iterator] for all members declared in this class or any of its
  /// augmentations.
  ///
  /// Duplicates and augmenting members are _not_ included.
  ///
  /// For instance:
  ///
  ///     class Class {
  ///       method() {} // Declared, so it is included.
  ///       method2() {} // Declared, so it is included.
  ///       method2() {} // Duplicate, so it is *not* included.
  ///     }
  ///
  ///     augment class Class {
  ///       augment method() {} // Augmenting, so it is *not* included.
  ///       extra() {} // Declared, so it is included.
  ///     }
  ///
  Iterator<T> fullMemberIterator<T extends Builder>();

  /// [NameIterator] for all members declared in this class or any of its
  /// augmentations.
  ///
  /// Duplicates and augmenting members are _not_ included.
  ///
  /// For instance:
  ///
  ///     class Class {
  ///       method() {} // Declared, so it is included.
  ///       method2() {} // Declared, so it is included.
  ///       method2() {} // Duplicate, so it is *not* included.
  ///     }
  ///
  ///     augment class Class {
  ///       augment method() {} // Augmenting, so it is *not* included.
  ///       extra() {} // Declared, so it is included.
  ///     }
  ///
  NameIterator<T> fullMemberNameIterator<T extends Builder>();
}

abstract class ClassBuilder implements DeclarationBuilder, ClassMemberAccess {
  /// The type in the `extends` clause of a class declaration.
  ///
  /// Currently this also holds the synthesized super class for a mixin
  /// declaration.
  TypeBuilder? get supertypeBuilder;

  /// The type in the `implements` clause of a class or mixin declaration.
  List<TypeBuilder>? get interfaceBuilders;

  @override
  Uri get fileUri;

  bool get isAbstract;

  bool get isSealed;

  bool get isBase;

  bool get isInterface;

  bool get isFinal;

  bool get declaresConstConstructor;

  bool get isMixinClass;

  bool get isMixinDeclaration;

  bool get isMixinApplication;

  bool get isAnonymousMixinApplication;

  TypeBuilder? get mixedInTypeBuilder;

  bool get isFutureOr;

  /// The [Class] built by this builder.
  ///
  /// For an augmentation class the origin class is returned.
  Class get cls;

  /// Reference for the class built by this builder.
  Reference get reference;

  abstract bool isNullClass;

  @override
  InterfaceType get thisType;

  Supertype buildMixedInType(
      LibraryBuilder library, List<TypeBuilder>? arguments);

  /// Looks up the member by [name] on the class built by this class builder.
  ///
  /// If [isSetter] is `false`, only fields, methods, and getters with that name
  /// will be found.  If [isSetter] is `true`, only non-final fields and setters
  /// will be found.
  ///
  /// If [isSuper] is `false`, the member is found among the interface members
  /// the class built by this class builder. If [isSuper] is `true`, the member
  /// is found among the class members of the superclass.
  ///
  /// If this class builder is an augmentation, interface members declared in
  /// this augmentation are searched before searching the interface members in
  /// the origin class.
  ///
  /// Unused in interface; left in on purpose.
  Member? lookupInstanceMember(ClassHierarchy hierarchy, Name name,
      {bool isSetter = false, bool isSuper = false});
}

abstract class ClassBuilderImpl extends DeclarationBuilderImpl
    implements ClassBuilder {
  @override
  bool isNullClass = false;

  InterfaceType? _legacyRawType;
  InterfaceType? _nullableRawType;
  InterfaceType? _nonNullableRawType;
  InterfaceType? _thisType;

  @override
  bool get isMixinApplication => mixedInTypeBuilder != null;

  @override
  bool get isAnonymousMixinApplication {
    return isMixinApplication &&
        // Coverage-ignore(suite): Not run.
        !isNamedMixinApplication;
  }

  @override
  LookupResult? findStaticBuilder(String name, int fileOffset, Uri fileUri,
      LibraryBuilder accessingLibrary) {
    if (accessingLibrary.nameOriginBuilder !=
            libraryBuilder.nameOriginBuilder &&
        name.startsWith("_")) {
      return null;
    }
    return nameSpace.lookupLocal(name,
        fileUri: fileUri, fileOffset: fileOffset, staticOnly: true);
  }

  @override
  Builder? lookupLocalMember(String name,
      {bool setter = false, bool required = false}) {
    Builder? builder = nameSpace.lookupLocalMember(name, setter: setter);
    if (required && builder == null) {
      internalProblem(
          templateInternalProblemNotFoundIn.withArguments(
              name, fullNameForErrors),
          -1,
          null);
    }
    return builder;
  }

  @override
  InterfaceType get thisType {
    return _thisType ??= new InterfaceType(cls, Nullability.nonNullable,
        getAsTypeArguments(cls.typeParameters, libraryBuilder.library));
  }

  // Coverage-ignore(suite): Not run.
  InterfaceType get legacyRawType {
    return _legacyRawType ??= new InterfaceType(cls, Nullability.legacy,
        new List<DartType>.filled(typeParametersCount, const DynamicType()));
  }

  InterfaceType get nullableRawType {
    return _nullableRawType ??= new InterfaceType(cls, Nullability.nullable,
        new List<DartType>.filled(typeParametersCount, const DynamicType()));
  }

  InterfaceType get nonNullableRawType {
    return _nonNullableRawType ??= new InterfaceType(
        cls,
        Nullability.nonNullable,
        new List<DartType>.filled(typeParametersCount, const DynamicType()));
  }

  InterfaceType rawType(Nullability nullability) {
    switch (nullability) {
      case Nullability.legacy:
        // Coverage-ignore(suite): Not run.
        return legacyRawType;
      case Nullability.nullable:
        return nullableRawType;
      case Nullability.nonNullable:
        return nonNullableRawType;
      // Coverage-ignore(suite): Not run.
      case Nullability.undetermined:
        return unhandled("$nullability", "rawType", TreeNode.noOffset, noUri);
    }
  }

  InterfaceType? aliasedTypeWithBuiltArgumentsCacheNonNullable;
  InterfaceType? aliasedTypeWithBuiltArgumentsCacheNullable;

  @override
  bool get isFutureOr {
    if (name == "FutureOr") {
      if (parent.importUri.isScheme("dart") &&
          parent.importUri.path == "async") {
        return true;
      }
    }
    return false;
  }

  @override
  DartType buildAliasedTypeWithBuiltArguments(
      LibraryBuilder library,
      Nullability nullability,
      List<DartType> arguments,
      TypeUse typeUse,
      Uri fileUri,
      int charOffset,
      {required bool hasExplicitTypeArguments}) {
    assert(cls.typeParameters.length == arguments.length);
    if (isNullClass) {
      return const NullType();
    }
    if (isFutureOr) {
      assert(arguments.length == 1);
      return new FutureOrType(arguments.single, nullability);
    }
    if (arguments.isEmpty) {
      return rawType(nullability);
    }
    if (aliasedTypeWithBuiltArgumentsCacheNonNullable != null &&
        // Coverage-ignore(suite): Not run.
        nullability == Nullability.nonNullable) {
      // Coverage-ignore-block(suite): Not run.
      assert(aliasedTypeWithBuiltArgumentsCacheNonNullable!.classReference ==
          cls.reference);
      assert(arguments.isEmpty);
      return aliasedTypeWithBuiltArgumentsCacheNonNullable!;
    } else if (aliasedTypeWithBuiltArgumentsCacheNullable != null &&
        // Coverage-ignore(suite): Not run.
        nullability == Nullability.nullable) {
      // Coverage-ignore-block(suite): Not run.
      assert(aliasedTypeWithBuiltArgumentsCacheNullable!.classReference ==
          cls.reference);
      assert(arguments.isEmpty);
      return aliasedTypeWithBuiltArgumentsCacheNullable!;
    }
    InterfaceType type = new InterfaceType(cls, nullability, arguments);
    if (arguments.isEmpty) {
      // Coverage-ignore-block(suite): Not run.
      assert(typeParametersCount == 0);
      if (nullability == Nullability.nonNullable) {
        aliasedTypeWithBuiltArgumentsCacheNonNullable = type;
      } else if (nullability == Nullability.nullable) {
        aliasedTypeWithBuiltArgumentsCacheNullable = type;
      }
    }

    if (typeParametersCount != 0 && library is SourceLibraryBuilder) {
      library.registerBoundsCheck(type, fileUri, charOffset, typeUse,
          inferred: !hasExplicitTypeArguments);
    }
    return type;
  }

  @override
  DartType buildAliasedType(
      LibraryBuilder library,
      NullabilityBuilder nullabilityBuilder,
      List<TypeBuilder>? arguments,
      TypeUse typeUse,
      Uri fileUri,
      int charOffset,
      ClassHierarchyBase? hierarchy,
      {required bool hasExplicitTypeArguments}) {
    if (name == "Record" &&
        libraryBuilder.importUri.scheme == "dart" &&
        libraryBuilder.importUri.path == "core" &&
        library is SourceLibraryBuilder &&
        !isRecordAccessAllowed(library)) {
      // Coverage-ignore-block(suite): Not run.
      library.reportFeatureNotEnabled(
          library.libraryFeatures.records, fileUri, charOffset, name.length);
      return const InvalidType();
    }
    return buildAliasedTypeWithBuiltArguments(
        library,
        nullabilityBuilder.build(),
        buildAliasedTypeArguments(library, arguments, hierarchy),
        typeUse,
        fileUri,
        charOffset,
        hasExplicitTypeArguments: hasExplicitTypeArguments);
  }

  @override
  Supertype buildMixedInType(
      LibraryBuilder library, List<TypeBuilder>? arguments) {
    if (arguments != null) {
      List<DartType> typeArguments =
          buildAliasedTypeArguments(library, arguments, /* hierarchy = */ null);
      typeArguments = unaliasTypes(typeArguments, legacyEraseAliases: false)!;
      return new Supertype(cls, typeArguments);
    } else {
      return new Supertype(
          cls,
          new List<DartType>.filled(
              cls.typeParameters.length, const UnknownType(),
              growable: true));
    }
  }

  @override
  String get fullNameForErrors {
    return isMixinApplication && !isNamedMixinApplication
        ? "${supertypeBuilder!.fullNameForErrors} with "
            "${mixedInTypeBuilder!.fullNameForErrors}"
        : name;
  }

  @override
  Member? lookupInstanceMember(ClassHierarchy hierarchy, Name name,
      {bool isSetter = false, bool isSuper = false}) {
    Class? instanceClass = cls;
    if (isSuper) {
      instanceClass = instanceClass.superclass;
      if (instanceClass == null) return null;
    }
    Member? target = isSuper
        ? hierarchy.getDispatchTarget(instanceClass, name, setter: isSetter)
        :
        // Coverage-ignore(suite): Not run.
        hierarchy.getInterfaceMember(instanceClass, name, setter: isSetter);
    if (isSuper && target == null) {
      if (cls.isMixinDeclaration) {
        target =
            hierarchy.getInterfaceMember(instanceClass, name, setter: isSetter);
      }
    }
    return target;
  }

  @override
  Nullability computeNullabilityWithArguments(List<TypeBuilder>? typeArguments,
      {required Map<TypeParameterBuilder, TraversalState>
          typeParametersTraversalState}) {
    if (isNullClass) {
      return Nullability.nullable;
    } else if (isFutureOr) {
      if (typeArguments != null && typeArguments.length == 1) {
        return typeArguments.single.computeNullability(
            typeParametersTraversalState: typeParametersTraversalState);
      } else {
        // This is `FutureOr<dynamic>`.
        return Nullability.nullable;
      }
    }
    return Nullability.nonNullable;
  }
}

class ConstructorRedirection {
  String target;
  bool cycleReported;

  ConstructorRedirection(this.target) : cycleReported = false;
}
